#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#include "gtthread.h"

#define num_of_phils 5
#define num_of_stks  5
const int HUNGRY = 0;
const int THINKING = 1;
const int EATING = 2;
const int MAX_THINKING = 5;

gtthread_mutex_t status;

typedef struct phil {
    int state;
    int id;
    int times;
} Phil;
Phil phils[num_of_phils];

typedef struct stk {
    gtthread_mutex_t lock;
    int id;
} Stick;
Stick stks[num_of_stks];

gtthread_t phils_t[num_of_phils];

inline int get_left(int i)
{
    return (i) % num_of_phils;
}

inline int get_right(int i)
{
    return (i + 1) % num_of_phils;
}


/**
 * About sleep():
 * According to sleep(3):
 *      sleep() makes the current PROCESS sleep until *seconds*
 * seconds have elapsed or a signal arrives which is not ignored.
 *
 * So if a thread calls sleep(), it is supposed to suspend the entire
 * process which has the calling thread.
 * In the implementation of pthread (glibc), each thread is created
 * by __clone() which is a system call (creates a lightweight process, that's a thread)
 * which can be put into sleep by sleep().
 * In my implementation, each thread is created not using __clone(), so the system is not aware of threads,
 * so each time calling sleep() will suspend the entire process.
 */
void think()
{
    int i = 0;
    int top = (rand() % 3) * 1000000;
    while (i < top) {
        if (i % 1000 == 0) {
            // do something
        }
        ++i;
    }
//    sleep(1); 
}

void take_chopstick(int id, int first, int second)
{
    gtthread_mutex_lock(&status);
    phils[id].state = HUNGRY;
    printf("Philosopher #%d is hungry\n", id);
    gtthread_mutex_unlock(&status);

    printf("Philosopher #%d tries to acquire the first chopstick %d\n", id, first);
    gtthread_mutex_lock(&stks[first].lock);
    printf("Philosopher #%d has got chopstick %d\n", id, first);
    printf("Philosopher #%d tries to acquire the second chopstick %d\n", id, second);
    gtthread_mutex_lock(&stks[second].lock);
    printf("Philosopher #%d has got chopstick %d\n", id, second);
}

void eat(int id)
{
    gtthread_mutex_lock(&status);
    phils[id].state = EATING;
    printf("Philosopher #%d is eating\n");
    gtthread_mutex_unlock(&status);
    think(); // Use think() as eat()
}

void drop_chopstick(int id, int first, int second)
{
    gtthread_mutex_lock(&status);
    phils[id].state = THINKING;
    printf("Philosopher #%d is thinking\n");
    gtthread_mutex_unlock(&status);

    gtthread_mutex_unlock(&stks[first].lock);
    printf("Philosopher #%d has releases chopstick %d\n", id, first);
    gtthread_mutex_unlock(&stks[second].lock);
    printf("Philosopher #%d has releases chopstick %d\n", id, second);
}

void dummy()
{
    int i = 0;
    int top = (rand() % 3) * 10000000;
    while (i < top) {
        if (i % 10000 == 0) {
            // do something
        }
        ++i;
    }
}

void *philosopher(void *a)
{
    int id = *(int*)a;
    int first = (id % 2) ? get_left(id) : get_right(id);
    int second = (id % 2) ? get_right(id) : get_left(id);
    printf("Philosopher #%d is running, first chopstick is %d, second chopstick is %d\n", id, first, second);
    while (phils[id].times < MAX_THINKING) {
        think();
        take_chopstick(id, first, second);
//        dummy();
        eat(id);
        drop_chopstick(id, second, first);
//        dummy();
        ++phils[id].times;
    }
}

int main()
{
    int i;
    srand(time(NULL));

    gtthread_init(1);
    puts("[main] Initialized gtthread");

    gtthread_mutex_init(&status);
    puts("[main] Initialized mutex status");

    for (i=0; i<num_of_stks; ++i) {
        stks[i].id = i;
        gtthread_mutex_init(&stks[i].lock);
    }

    puts("[main] Initialized mutex sticks");
    for (i=0; i<num_of_phils; ++i) {
        phils[i].state = THINKING;
        phils[i].id = i;
        phils[i].times = 0;
        gtthread_create(&phils_t[i], philosopher, (void*)&phils[i].id);
    }
    for (i=0; i<num_of_phils; ++i) {
        gtthread_join(phils_t[i], NULL);
    }
    puts("[main] returns");
    return 0;
}


